#include <any>
#include <cstdio>
#include <memory>
#include <string>
#include <map>

#include <refbase.h>

class Object : public RefBase {
public:
    Object(const char *name = nullptr)
    {
        pname = name;
        if (pname != nullptr) {
            printf("+ %s\n", pname);
        }
    }

    ~Object()
    {
        if (pname != nullptr) {
            printf("- %s\n", pname);
        }
    }

private:
    const char *pname = nullptr;
};

std::map<std::string, std::any> anyMap;

void SetGlobalData(const std::string &dataName, std::any data)
{
    printf("%s\n", data.type().name());
    anyMap[dataName] = data;
    printf("%s\n", anyMap[dataName].type().name());
}

std::any GetGlobalData(const std::string &dataName)
{
    auto it = anyMap.find(dataName);
    if (it == anyMap.end()) {
        return nullptr;
    }
    return it->second;
}

template<class T>
static void SetData(const std::string &dataName, std::shared_ptr<T> ptr)
{
    printf("%s\n", __PRETTY_FUNCTION__);
    printf("%s\n", typeid(ptr).name());
    SetGlobalData(dataName, ptr);
}

template<class T>
static std::shared_ptr<T> GetData(const std::string &dataName)
{
    printf("%s\n", __PRETTY_FUNCTION__);
    using sharedPtrT = std::shared_ptr<T>;
    const auto &data = GetGlobalData(dataName);
    printf("%s\n", data.type().name());
    return std::any_cast<sharedPtrT>(data);
}

#define DEFINE_GLOBAL_DATA(name, ...) \
    using Type##name = __VA_ARGS__; \
    SingletonDataDelegator<Type##name> name{#name}

template<class T>
class SingletonDataDelegator {
public:
    SingletonDataDelegator(const std::string &name) : name_(name)
    {
        auto ptr = std::make_shared<T>();
        SetData<T>(name_, ptr);
    }
    ~SingletonDataDelegator() = default;

    std::shared_ptr<T> GetPtr()
    {
        return GetData<T>(name_);
    }

    T &Get()
    {
        return *GetData<T>(name_);
    }

private:
    std::string name_;
};

int main()
{
    printf("class Object{} typeid name: %s\n", typeid(Object).name());

    int count = 1;

    // normal
    {
        std::any a = 1; // int
        printf("%d. (%s), ", count++, a.type().name());

        auto b = std::any_cast<int>(a);
        printf("any_cast<int>(a) = %d\n", b);
    }

    // exception
    {
        std::any a = 1.0; // double
        printf("%d. (%s), ", count++, a.type().name());

        try {
            auto b = std::any_cast<int>(a);
            printf("any_cast<int>(a) = %d\n", b);
        } catch (std::bad_any_cast& e) {
            printf("catch bad_any_cast\n");
        }
    }

    // judge
    {
        std::any a = 1.0; // double
        printf("%d. (%s), ", count++, a.type().name());

        if (a.type() == typeid(int)) {
            auto b = std::any_cast<int>(a);
            printf("any_cast<int>(a) = %d\n", b);
        } else {
            printf("isn't int\n");
        }
    }

    // pointer
    {
        std::any a = 1; // int
        printf("%d. (%s), ", count++, a.type().name());

        auto b = std::any_cast<int>(&a);
        printf("any_cast<int>(&a) = %p\n", b);
    }

    // pointer, nullptr
    {
        std::any a = 1.0; // double
        printf("%d. (%s), ", count++, a.type().name());

        auto b = std::any_cast<int>(&a);
        printf("any_cast<int>(&a) = %p\n", b);
    }

    {
        std::any a = 1; // int
        printf("%d. (%s), ", count++, a.type().name());
        printf("have: %d\n", a.has_value());

        a.reset();
        printf("%d. (%s), ", count++, a.type().name());
        printf("have: %d\n", a.has_value());

        printf("%d. (%s), ", count++, a.type().name());
        auto b = std::any_cast<int>(&a);
        printf("any_cast<int>(&a) = %p\n", b);
    }

    {
        auto sptr = std::make_shared<Object>("a");
        printf("count: %ld\n", sptr.use_count());
        std::any a = sptr;
        printf("%d. (%s), ", count++, a.type().name());
        printf("have: %d\n", a.has_value());
        printf("count: %ld\n", sptr.use_count());
        a.reset();
        printf("%d. (%s), ", count++, a.type().name());
        printf("have: %d\n", a.has_value());
        printf("count: %ld\n", sptr.use_count());
    }

    {
        sptr<Object> sptr = new Object("a");
        printf("before\n");
        std::any a = sptr;
        printf("%d. (%s), ", count++, a.type().name());
        printf("have: %d\n", a.has_value());
        printf("mid\n");
        a.reset();
        printf("%d. (%s), ", count++, a.type().name());
        printf("have: %d\n", a.has_value());
        printf("after\n");
    }

    {
        DEFINE_GLOBAL_DATA(onReleaseFuncs, std::map<int, int>);
        onReleaseFuncs.Get()[1] = 1;
    }
    return 0;
}
